#include "BackupManager.h"
#include "Poco\Path.h"
#include "Poco\File.h"
#include "Poco\Timestamp.h"
#include "Poco\Util\Application.h"
#include "Poco\Zip\ZipStream.h"
#include "Cryptoer.h"
#include "Poco\StreamCopier.h"
#include <string>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include "Poco\FileStream.h"
#include "Poco\Zip\Compress.h"
#include "Poco\Zip\ZipCommon.h"
#include "Poco\Zip\ZipArchive.h"
#include "Poco\RecursiveDirectoryIterator.h"
#include "Util.h"

using Poco::Path;
using Poco::File;
using Poco::Timestamp;
using std::string;
using std::stringstream;
using std::map;
using std::vector;
using Poco::Util::Application;
using Poco::Zip::Compress;
using Poco::Zip::ZipCommon;
using Poco::Zip::ZipArchive;
using Poco::Zip::ZipInputStream;
using Poco::SiblingsFirstRecursiveDirectoryIterator;
BackupManager::BackupManager()
{
}


BackupManager::~BackupManager()
{
}



void BackupManager::backup()
{
	Application& app = Application::instance();

	Path srcPath(Path().absolute().pushDirectory(app.config().getString("src")));
	string srcpname = srcPath.toString();
	Path backPath = Util::getBackupPath();
	backPath.makeDirectory();
	srcPath.makeDirectory();
	File fsrc(srcPath);
	File fback(backPath);


	map<string, FileInfo> om;//old lastmodified map;
	map<string, FileInfo> nm;//new one;
	time_t cur = Timestamp().epochMicroseconds();
	Path zipPath = Path(createZipFileName(cur));

	std::vector<File> files;
	Util::listDirFiles(srcPath.toString(), files);
	Cryptoer *pc = Util::getCurrentCryptoer();
	createFileList(nm, cur);
	if (!fback.exists()) {
		fback.createDirectories();
		std::ofstream out(zipPath.toString(), std::ios::binary);
		Compress c(out, true);
		for (std::vector<File>::iterator it = files.begin(); it != files.end(); it++) {
			std::ifstream fs(it->path().c_str(), std::ios::binary/*std::fstream::in | std::fstream::out*/);
			stringstream osstr;
			pc->rawEncodeNonBase64(fs, osstr);
			string fn = it->path().substr(srcpname.length());
			c.addFile(osstr, it->getLastModified(), Path(fn));
		}

		c.addFile(writeFileList(nm), Poco::DateTime(), "metadata");
		c.close();
		return;
	}
	else {

		string latest = getLatestBackupFile();

		if (!latest.empty()) {
			std::ifstream inp(latest.c_str(), std::ios::binary);
			ZipArchive arch(inp);
			ZipArchive::FileHeaders::const_iterator it = arch.findHeader("metadata");
			ZipInputStream zipin(inp, it->second);
			readFileList(zipin, om);
			vector<string> dif;
			compareFileList(om, nm, dif);
			if (dif.size() == 0) {
				app.logger().information("All file upto date, nothing to do. Bye~");
				return;
			}

			std::ofstream out(zipPath.toString(), std::ios::binary);
			Compress c(out, true);
			for (vector<string>::iterator it = dif.begin(); it != dif.end(); it++) {
				File file(srcpname + (*it));
				std::ifstream fs(file.path().c_str(), std::ios::binary/*std::fstream::in | std::fstream::out*/);
				stringstream osstr;
				pc->rawEncodeNonBase64(fs, osstr);
				c.addFile(osstr, file.getLastModified(), Path(*it));
			}

			c.addFile(writeFileList(nm), Poco::DateTime(), "metadata");
			c.close();


			inp.close();
		}
		else {
			app.logger().information("Backup dir not exist, please check config-file for 'backup-dir'");
		}

	}

}

void BackupManager::restore(time_t t)
{
	Application& app = Application::instance();
	Path backPath = Util::getBackupPath();


	string backver;
	if (t == 0) {
		backver = getLatestBackupFile();
	}
	else {
		backver = createZipFileName(t);
	}
	File fback(backver);
	if (!fback.exists()) {
		app.logger().information("%s not exist", backver);
		return;
	}
	FileInfoMap fm;
	std::ifstream inp(backver.c_str(), std::ios::binary);
	ZipArchive arch(inp);
	ZipArchive::FileHeaders::const_iterator it = arch.findHeader("metadata");
	ZipInputStream zipin(inp, it->second);
	readFileList(zipin, fm);


	std::vector<File> files;
	Path srcPath(Path().absolute().pushDirectory(app.config().getString("src")));
	Util::listDirFiles(srcPath.toString(), files);
	//remove
	for (vector<File>::iterator it = files.begin(); it != files.end(); it++) {
		string key = it->path().substr(srcPath.toString().length());
		FileInfoMapIterator mit = fm.find(key);
		if (mit == fm.end()) {
			app.logger().information("Remove %s", key);
			it->remove();
		}
	}

	map<time_t, vector<FileInfo>> needupdate;
	for (FileInfoMapCIterator it = fm.begin(); it != fm.end(); it++) {
		Path psrc(srcPath.toString() + it->first);
		File fsrc(psrc);
		if (fsrc.exists()) {
			if (fsrc.getLastModified().epochMicroseconds() != it->second.lm ||
				fsrc.getSize() != it->second.sz) {
				app.logger().information("Update %s", it->first);
				if (needupdate.find(it->second.loc) == needupdate.end()) {
					needupdate[it->second.loc] = vector<FileInfo>();
				}
				needupdate[it->second.loc].push_back(it->second);
			}
			else {
				app.logger().information("Skip %s", it->first);
			}
		}
		else {
			app.logger().information("Add %s", it->first);
			if (needupdate.find(it->second.loc) == needupdate.end()) {
				needupdate[it->second.loc] = vector<FileInfo>();
			}
			needupdate[it->second.loc].push_back(it->second);
		}

	}
	Cryptoer *pc = Util::getCurrentCryptoer();
	for (map<time_t, vector<FileInfo>>::const_iterator it = needupdate.begin();
		it != needupdate.end(); it++) {
		Path srcPath(Path().absolute().pushDirectory(app.config().getString("src")));
		Path backPath = Util::getBackupPath();
		string zipPath = createZipFileName(it->first);
		std::ifstream inp(zipPath.c_str(), std::ios::binary);
		ZipArchive arch(inp);
		for (vector<FileInfo>::const_iterator fit = it->second.begin();
			fit != it->second.end(); fit++) {
			Path fn(fit->fn);
			string fn_linx_style = fn.toString(Path::Style::PATH_UNIX);
			ZipArchive::FileHeaders::const_iterator fhit = arch.findHeader(fn_linx_style);
			ZipInputStream zipin(inp, fhit->second);
			Path psrc(srcPath, Path(fit->fn));
			std::ofstream os(psrc.toString(), std::ios::binary);
			pc->rawDecodeNonBase64(zipin, os);
			os.close();
			File fsrc(psrc);
			fsrc.setLastModified(Timestamp(fit->lm));
		}
		inp.close();
	}
	inp.close();
}



std::string BackupManager::createZipFileName(const time_t& cur)
{
	Application& app = Application::instance();
	Path backPath = Util::getBackupPath();
	char buf[64];
	string curstr(_ui64toa(cur, buf, 10));
	stringstream sstr;
	sstr << curstr << ".zip";
	backPath.setFileName(sstr.str());
	return backPath.toString();
}

std::string BackupManager::getLatestBackupFile()
{
	string latest;
	Application& app = Application::instance();
	Path backPath = Util::getBackupPath();
	File fback(backPath);
	if (!fback.exists()) {
		app.logger().information("Backup dir not exist, please check config-file for 'backup-dir'");
		return latest;
	}
	std::vector<File> backfiles;
	fback.list(backfiles);
	unsigned long long latestMod = 0;
	for (std::vector<File>::iterator it = backfiles.begin(); it != backfiles.end(); it++) {
		if (it->isFile() && it->getSize() > 0) {
			Path pf(it->path());
			char* chEnd = NULL; //memory leak?
			unsigned long long cur = _strtoui64(pf.getBaseName().c_str(), &chEnd, 10);
			if (latestMod < cur) {
				latestMod = cur;
				latest = it->path();
			}
		}
	}
	return latest;
}

std::stringstream BackupManager::writeFileList(const FileInfoMap& m)
{
	stringstream sstr;
	sstr << m.size() << "\r\n";
	for (FileInfoMapCIterator it = m.begin(); it != m.end(); it++) {
		sstr << it->first
			<< ","
			<< it->second.lm
			<< ","
			<< it->second.sz
			<< ","
			<< it->second.loc
			<< "\r\n";
	}
	return sstr;
}

void BackupManager::createFileList(FileInfoMap& m, time_t cur)
{
	Application& app = Application::instance();
	Path path(Path().absolute().pushDirectory(app.config().getString("src")));
	std::vector<File> files;
	Util::listDirFiles(path.toString(), files);
	for (std::vector<File>::iterator it = files.begin(); it != files.end(); it++) {
		m[it->path().substr(path.toString().length())]
			= FileInfo(it->path().substr(path.toString().length()), it->getLastModified().epochMicroseconds(), it->getSize(), cur);
	}
}

void BackupManager::readFileList(std::istream& s, FileInfoMap& m)
{
	Application& app = Application::instance();
	char buf[1024];
	s.getline(buf, 1024);
	int len = ::atoi(buf);
	for (int i = 0; i < len; i++) {
		s.getline(buf, 1024);
		string line(buf);
		auto cpos1 = line.find_first_of(',', 0);
		auto cpos2 = line.find_first_of(',', cpos1 + 1);
		auto cpos3 = line.find_first_of(',', cpos2 + 1);
		string fp = line.substr(0, cpos1);
		string lmstr = line.substr(cpos1 + 1, cpos2);
		string szstr = line.substr(cpos2 + 1, cpos3);
		string locstr = line.substr(cpos3 + 1);
		char *endPtr;
		unsigned long long lm = _strtoui64(lmstr.c_str(), &endPtr, 10);
		unsigned long long sz = _strtoui64(szstr.c_str(), &endPtr, 10);
		unsigned long long loc = _strtoui64(locstr.c_str(), &endPtr, 10);
		m[fp] = FileInfo(fp, lm, sz, loc);
		//app.logger().information(string(fp));
		//app.logger().information("%z", lm);
		//app.logger().information("%z", sz);
		//app.logger().information("%z", loc);
	}
}

void BackupManager::compareFileList(const FileInfoMap& om, FileInfoMap& nm, std::vector<std::string> &dif)
{
	for (FileInfoMapIterator it = nm.begin(); it != nm.end(); it++) {
		FileInfoMapCIterator oit;
		if ((oit = om.find(it->first)) != om.end()) {
			FileInfo ofi = oit->second;
			FileInfo nfi = it->second;
			if (nfi.lm > ofi.lm) {
				dif.push_back(it->first);
			}
			else {
				it->second.loc = oit->second.loc;
			}
		}
		else {//new file
			dif.push_back(it->first);
		}
	}
}


